/**
 * Functions save in db
 * */
var EasyMongo = require('../../common').EasyMongo;

var analyzerName = "iview-analyzer";
initAnalyzer(analyzerName, "iview-data", "iview");

/**
 *  @param analyzer name,meta to save target data, executors to deal source data
 * */
function initAnalyzer(name, metaName, executors) {
    EasyMongo.remove('analyzer', {name: name}, function () {
        EasyMongo.add('analyzer', {
            name: name,
            meta: metaName,
            executorSeries: executors,
            method: targetExecutor,
            cron: {
                start: new Date(),
                freq: "0 0 8 * * *",
                monitor: "0 * * * * *"
            }
        }, function (err, id) {
            console.log(id);
        });
    });
}

var targetExecutor = function () {
    var self = this;
    self.EasyMongo.find('iview-data', {}, function (err, docs) {
        if (err) {
            console.log("IView Target Executor: DB ERROR in checking progress.");
        } else {
            /* 1. raw check */
            /**
             *  @algorithm
             *  unique key is a vector with 4 dimension,
             *  first 3 dimension for each ,compare like following, then calculate by weight
             *  [ exception name dimension, trace position dimension,error message dimension ]
             *  type -> eg:NullPointerException - if same 1, else -1
             *  trace -> eg:mm174 ,nn245,gg053 - concat and count Levenshtein distance
             *  msg -> eg:error Code 1400 - count Levenshtein distance
             *
             *  if(similarity > K) {
             *    delete raw item
             *    adjust times weekly++,total++
             *    set last appear time
             *    if(solved) set status = again
             *  }
             *  else { set to !raw and other time properties }
             * */
            /* Define Consts & Weight Table */
            var WEEK_SEC = 7*24*3600*1000;
            var ALL_MAILER = "WPSUPP";
            var MAIL_ON_OFF = "ON";
            var weightTable = {
                uniqueRegex: {
                    exceptionTrace: [],
                    exceptionMessage: ["ORA-\\d{5}", "Missing descriptor"]
                },
                exceptionWeight: {
                    "DatabaseException": 0,
                    "ExecutorFailureException": 0,
                    "RuntimeException": 1,
                    "NullPointerException": 1,
                    "Default": 2,
                    "RemoteException": 3,
                    "NoSuchMethodException": 4,
                    "IllegalArgumentException": 5,
                    "TokenExpirationException": 10,
                    "InvalidTokenException": 10,

                    "Impact": 0.92,
                    "Scale": 10
                },
                dimensionWeight: {
                    exceptionMsgSimilarity: 1,
                    traceSimilarity: 2
                },

                critical_K: 0.95,
                special_K: 0.98
            };

            /* Levenshtein Algorithm */
            var Levenshtein = {
                _str1: null,
                _str3: null,
                _matrix: null,
                _isString: function (s) {
                    return Object.prototype.toString.call(s) === '[object String]';
                },
                _isNumber: function (s) {
                    return Object.prototype.toString.call(s) === '[object Number]';
                },
                init: function (str1, str2) {
                    if (!this._isString(str1) || !this._isString(str2)) return;

                    this._str1 = str1;
                    this._str2 = str2;

                    str1.length && str2.length && this._createMatrix(str1.length + 1, str2.length + 1);
                    this._matrix && this._initMatrix();

                    return this;
                },
                get: function () {
                    return 1 - this._getDistance() / Math.max(this._str1.length, this._str2.length);
                },
                _getDistance: function () {
                    var len1 = this._str1.length,
                        len2 = this._str2.length;

                    if (!len1 || !len2) return Math.max(len1, len2);

                    var str1 = this._str1.split(''),
                        str2 = this._str2.split('');

                    var i = 0, j = 0, temp = 0;
                    while (i++ < len1) {
                        j = 0;
                        while (j++ < len2) {
                            temp = str1[i - 1] === str2[j - 1] ? 0 : 1;
                            this._matrix[i][j] = Math.min(this._matrix[i - 1][j] + 1, this._matrix[i][j - 1] + 1, this._matrix[i - 1][j - 1] + temp);
                        }
                    }
                    return this._matrix[i - 1][j - 1];
                },
                _initMatrix: function () {
                    var cols = this._matrix[0].length,
                        rows = this._matrix.length;
                    var l = Math.max(cols, rows);
                    while (l--) {
                        cols - 1 >= l && (this._matrix[0][l] = l);
                        rows - 1 >= l && (this._matrix[l][0] = l);
                    }
                },
                _createMatrix: function (n, m) {
                    if (!this._isNumber(n) || !this._isNumber(m) || n < 1 || m < 1) return;

                    this._matrix = new Array(n), i = 0;
                    while (i < n) this._matrix[i++] = new Array(m);
                }
            };

            console.log("Target Analyze : Step 1 -> Sort by time ASC");
            docs.sort(function (a, b) {
                return a.lastAppear - b.lastAppear;
            });

            console.log("Target Analyze : Step 2 -> Build NoneRaw & Raw Vector");
            var cachedNotRaw = {};
            var cachedRaw = [], cursor = 0;   //must be sorted, so use Array instead of Object

            for (var i = 0; i < docs.length; i++) {
                buildVector(docs[i]);
                if (docs[i].isRaw == '1') {
                    cachedNotRaw[i+""] = docs[i].exceptionKey;
                } else {
                    cachedRaw.push({id: i, vector: docs[i].exceptionKey});
                }
            }

            console.log("Target Analyze : Step 3 -> Recursive Deal Raw Items");
            var again = [],notice = "";
            (function recursiveMerge() {
                if (cursor < cachedRaw.length) {
                    var mergeId = "";
                    var similarity;
                    for (var i in cachedNotRaw) {
                        similarity = calculateSimilarity(cachedNotRaw[i], cachedRaw[cursor].vector);
                        console.log("Target Analyze : Calculated Similarity -> "+similarity);
                        if (similarity > weightTable.critical_K) {
                            mergeId = i;
                            break;
                        }
                    }
                    if (mergeId) {
                        merge(mergeId, cachedRaw[cursor].id, function (err) {
                            if (err) console.log("Target Analyze : Merge ERROR");
                            else {
                                cursor++;
                                recursiveMerge();
                            }
                        });
                    } else {
                        cachedNotRaw[cachedRaw[cursor].id + ""] = cachedRaw[cursor].vector;
                        modify(cachedRaw[cursor].id, function (err) {
                            if (err) console.log("Target Analyze : Merge ERROR");
                            else {
                                cursor++;
                                recursiveMerge();
                            }
                        });
                    }
                } else {
                    if(MAIL_ON_OFF != "ON") {
                        console.log("Target Analyze : Finish All Process.");
                        return;
                    }
                    console.log("Target Analyze : Finish All Process. Start mail notice.");

                    function sendMail(sendTo,html){
                        sendTo = sendTo.replace("-",".");
                        var monitorMail = {
                            from: "Exception_Eye@oocl.com",
                            to : sendTo+"@oocl.com",
                            subject: "IView Exception NOTICE",
                            generateTextFromHTML : true,
                            html:html
                        };

                        var transport = self.mailer.createTransport("SMTP", {
                            host: "smtpapp1",
                            port: 25
                        });
                        console.log("Mail Content : " + monitorMail);
                        transport.sendMail(monitorMail, function(error, response){
                            if(error){
                                console.log("Monitor Mailer : ERROR " + error);
                            }else{
                                console.log("Monitor Mailer : Message sent: " + response.message+"\n");
                            }
                            transport.close();
                        });
                    }
                    again.forEach(function(e){
                        var tempHtml = "<h2>异常再次出现</h2><h3>异常类型 : "+e.type+"</h3><h4>已出现 : "+ e.times+
                            "次(本周/全部)&nbsp;&nbsp;&nbsp;&nbsp;最近发生时间:"+ e.lastAppear+"</h4><p>异常信息 : " + "<span>"+e.trace + "</span></p>";
                        sendMail(e.owner || ALL_MAILER,tempHtml);
                    });

                    if(notice.length != 0) {
                        sendMail(ALL_MAILER,notice);
                    }
                }
            })();

            function merge(targetPos, sourceIndex, callback) {
                console.log("Target Analyze : Step 4.1 -> Merge Same Items");
                targetPos = parseInt(targetPos);
                var currentDate = new Date().valueOf();

                var targetDate =  docs[targetPos].lastAppear;
                var sourceDate =  docs[sourceIndex].lastAppear;
                var status = docs[targetPos].status;

                var times = docs[targetPos].times.split("/");

                if(currentDate - sourceDate < WEEK_SEC) {
                    times[0] = (parseInt(times[0]) + 1) + "";
                }
                if(currentDate - targetDate > WEEK_SEC) {
                    if(times[0] != "0") {
                        times[0] = (parseInt(times[0]) - 1) + "";
                    }
                }
                times[1] = parseInt(times[1]) + 1;
                docs[targetPos].times = times[0] + "/"+ times[1];
                docs[targetPos].lastAppear = sourceDate;

                docs[targetPos].exceptionKey = docs[sourceIndex].exceptionKey;
                docs[targetPos].trace = docs[sourceIndex].trace;

                if(status == 'finish') {
                    docs[targetPos].status = 'again';
                    again.push({
                        type : docs[targetPos].exceptionType,
                        times : docs[targetPos].times,
                        trace : docs[targetPos].trace,
                        owner : docs[targetPos].owner,
                        lastAppear : new Date(sourceDate)
                    });
                }

                var updateObj = self._.pick(docs[targetPos],["exceptionKey","trace","status","times","lastAppear"]);
                updateObj.exceptionKey.pop();
                updateObj.exceptionKey.pop();

                self.EasyMongo.update('iview-data',{_id:docs[targetPos]._id},updateObj,function(err){
                    if(err) callback(err);
                    else {
                        self.EasyMongo.remove("iview-data",{_id:docs[sourceIndex]._id},function(err){
                           err ? callback(err) : callback(null);
                        });
                    }
                });
            }

            function modify(objIndex, callback) {
                console.log("Target Analyze : Step 4.2 -> Modify Properties");
                var appear = docs[objIndex].lastAppear;
                var updateObj = {isRaw : "1"};
                if(new Date().valueOf() - appear > WEEK_SEC) {
                    updateObj.times = "0/1";
                } else {
                    notice += "<h3>异常类型 : "+docs[objIndex].exceptionType+"</h3><p>出现时间 : "+new Date(appear)+
                        "</p><br /><p>异常信息 : " + "<span>"+docs[objIndex].trace + "</span></p>";
                }
                self.EasyMongo.update('iview-data',{_id:docs[objIndex]._id},updateObj,function(err){
                    err ? callback(err) : callback(null);
                });
            }

            function buildVector(obj) {
                obj.exceptionKey.push(obj.exceptionType,
                    (obj.exceptionType in weightTable.exceptionWeight ?
                            weightTable.exceptionWeight[obj.exceptionType] :
                            weightTable.exceptionWeight['Default']
                    ));
            }

            /**
             * @param vector1 is data in dictionary,vector2 is vector to compare
             * */
            function calculateSimilarity(vector1, vector2) {
                if (vector1[2] !== vector2[2]) {
                    return 0;
                } else {
                    var d1 = Levenshtein.init(vector1[0], vector2[0]).get();
                    weightTable.uniqueRegex.exceptionMessage.forEach(function (reg) {
                        var regex = new RegExp(reg);
                        if (regex.test(vector2[0])) {
                            d1 < weightTable.special_K && (d1 = 0);
                        }
                    });

                    var d2 = Levenshtein.init(vector1[1], vector2[1]).get();
                    weightTable.uniqueRegex.exceptionTrace.forEach(function (reg) {
                        var regex = new RegExp(reg);
                        if (regex.test(vector2[1])) {
                            d2 < weightTable.special_K && (d2 = 0);
                        }
                    });

                    var avg = (d1 * weightTable.dimensionWeight.exceptionMsgSimilarity + d2 * weightTable.dimensionWeight.traceSimilarity) /
                        (weightTable.dimensionWeight.exceptionMsgSimilarity + weightTable.dimensionWeight.traceSimilarity);
                    avg += (1 - avg) * (vector1[3] / weightTable.exceptionWeight.Scale * weightTable.exceptionWeight.Impact);
                    return avg;
                }
            }
        }
    });
};